﻿using BridgeWebSystemLib.Core.DataEntity;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Text;

namespace BridgeWebSystemLib.Core.DataAccess
{
    public static class DataAccessCache
    {
        private static readonly ConcurrentDictionary<string, string> TypeTableName = new ConcurrentDictionary<string, string>();
        private static readonly ConcurrentDictionary<string, IEnumerable<PropertyInfo>> KeyProperties = new ConcurrentDictionary<string, IEnumerable<PropertyInfo>>();
        private static readonly ConcurrentDictionary<string, IEnumerable<PropertyInfo>> FieldProperties = new ConcurrentDictionary<string, IEnumerable<PropertyInfo>>();
        private static readonly ConcurrentDictionary<string, IEnumerable<PropertyInfo>> ExFieldProperties = new ConcurrentDictionary<string, IEnumerable<PropertyInfo>>();
        private static readonly ConcurrentDictionary<string, IEnumerable<PropertyInfo>> ComputedProperties = new ConcurrentDictionary<string, IEnumerable<PropertyInfo>>();

        public static string GetTableName(Type type)
        {
            if (TypeTableName.TryGetValue(type.FullName, out string name))
                return name;
            var info = type;
            var tableAttrName = info.GetCustomAttribute<TableAttribute>(false)?.Name
                ?? (info.GetCustomAttributes(false).FirstOrDefault(attr => attr.GetType().Name == "TableAttribute") as dynamic)?.Name;
            if (tableAttrName != null)
            {
                name = tableAttrName;
            }
            else
            {
                Type propType = GetEntityType(type);
                name = propType.Name;
                if (type.IsInterface && name.StartsWith("I"))
                    name = name.Substring(1);
            }
            TypeTableName[type.FullName] = name;
            return name;
        }

        public static PropertyInfo GetSingleKey<T>()
        {
            var type = typeof(T);
            var keys = KeyPropertiesCache(type);
            if (keys.Count > 1)
                throw new DataException($"{type.ToString()}只支持单一主键！");
            else if (keys.Count == 0)
                return null;
            return keys[0];
        }

        public static List<PropertyInfo> KeyPropertiesCache(Type type)
        {
            if (KeyProperties.TryGetValue(type.FullName, out IEnumerable<PropertyInfo> pi))
            {
                return pi.ToList();
            }
            var allProperties = EntityFieldPropertiesCache(type);
            var keyProperties = allProperties.Where(p => p.GetCustomAttributes(true).Any(a => a is KeyAttribute)).ToList();
            if (keyProperties.Count == 0)
            {
                //var idProp = allProperties.Find(p => p.Name.ToUpper().Contains("ID"));
                //if (idProp != null)
                //{
                //    keyProperties.Add(idProp);
                //}
                //else
                //{
                //    var noProp = allProperties.Find(p => p.Name.ToUpper().Contains("NO"));
                //    if (noProp != null)
                //    {
                //        keyProperties.Add(noProp);
                //    }
                //}
            }
            KeyProperties[type.FullName] = keyProperties;
            return keyProperties;

        }

        public static List<PropertyInfo> EntityFieldPropertiesCache(Type type)
        {
            Type entityType = GetEntityType(type);
            if (FieldProperties.TryGetValue(entityType.FullName, out IEnumerable<PropertyInfo> pis))
            {
                return pis.ToList();
            }
            if (!typeof(BaseEntity).IsAssignableFrom(type))
            {
                throw new DataException($"{type.ToString()}必须继承自BaseEntity！");
            }
            var properties = entityType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(IsWriteable).ToArray();
            FieldProperties[entityType.FullName] = properties;
            return properties.ToList();
        }

        public static List<PropertyInfo> EntityExFieldPropertiesCache(Type type)
        {
            if (ExFieldProperties.TryGetValue(type.FullName, out IEnumerable<PropertyInfo> pis))
            {
                return pis.ToList();
            }
            if (!typeof(BaseEntity).IsAssignableFrom(type))
            {
                throw new DataException($"{type.ToString()}必须继承自BaseEntity！");
            }
            var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(IsWriteable).ToArray();
            ExFieldProperties[type.FullName] = properties;
            return properties.ToList();
        }

        private static Type GetEntityType(Type type)
        {
            if (type.BaseType == typeof(BaseEntity))
                return type;
            else
            {
                return GetEntityType(type.BaseType);
            }
        }

        public static List<PropertyInfo> ComputedPropertiesCache(Type type)
        {
            if (ComputedProperties.TryGetValue(type.FullName, out IEnumerable<PropertyInfo> pi))
            {
                return pi.ToList();
            }
            var computedProperties = EntityFieldPropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ComputedAttribute)).ToList();
            ComputedProperties[type.FullName] = computedProperties;
            return computedProperties;
        }


        public static bool IsWriteable(PropertyInfo pi)
        {
            var attributes = pi.GetCustomAttributes(typeof(ExtendedAttribute), false).ToList();
            if (attributes.Count != 1)
                return true;
            return false;
        }
    }
}
